package com.oj.controller;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.oj.config.OJConfig;
import com.oj.entity.*;
import com.oj.exception.OJException;
import com.oj.mapper.*;
import com.oj.service.ContestService;
import com.oj.service.ProblemService;
import com.oj.service.UserService;
import com.oj.util.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.*;

import static com.oj.util.Html2Text.convertHtmlToMarkdown;

@Slf4j
@Controller
@RequestMapping
public class ProblemController {
    @Autowired
    private ProblemService problemService;

    @Autowired
    private ProblemMapper problemMapper;

    @Autowired
    private SolutionMapper solutionMapper;

    @Autowired
    private UserService userService;

    @Autowired
    private ContestService contestService;

    @Autowired
    private ContestProblemMapper contestProblemMapper;

    @Autowired
    private TagsMapper tagsMapper;

    @Autowired
    private TagsviewMapper tagsviewMapper;

    @Autowired
    private PrivilegeMapper privilegeMapper;

    @ResponseBody
    @RequestMapping(value = {"/api/admin/problem/search"}, method = RequestMethod.GET)
    public Result<List> apiAdminProblems(String q, Integer limit, Integer offset){
        if (limit == null) {
            limit = 0;
        }
        if (offset == null) {
            offset = 0;
        }
        return Result.success(problemService.searchAdmin(q, limit, offset));
    }
    @ResponseBody
    @RequestMapping(value = {"/api/problem/search"}, method = RequestMethod.GET)
    public Result<List> apiProblems(String q, Integer limit, Integer offset, HttpServletRequest request){
        if (limit == null) {
            limit = 0;
        }
        if (offset == null) {
            offset = 0;
        }
        User user = userService.getUserBySession(request);
        List<Problem> problems = problemService.search(q, limit, offset);
        for (Problem problem: problems.subList(0, problems.size())) {
            List<Tags> tags = new ArrayList<>();
            for (Tagsview tagsview: tagsviewMapper.queryByProblems(problem.getProblem_id())) {
                tags.add(tagsMapper.query(tagsview.getTag_id()));
            }
            problem.setTags(tags);
            problem.setStatus(0);
            if (user != null) {
                if (solutionMapper.getProblemTriedCount(user.getUsername(), problem.getProblem_id(), 0) > 0) {
                    if (solutionMapper.getProblemSolvedCount(user.getUsername(), problem.getProblem_id(), 0) > 0) {
                        problem.setStatus(1);
                    } else {
                        problem.setStatus(2);
                    }
                }
            }
        }
        return Result.success(problems);
    }

    @GetMapping(value = {"/problemset",
            "/problemset/page/{pageNum}"})
    public ModelAndView problems(@PathVariable(value = "pageNum",required = false)Integer pageNum,
                                 @RequestParam(value = "pageSize",required = false, defaultValue = "50")Integer pageSize,
                                 @RequestParam(value = "tag",required = false, defaultValue = "")String tag,
                                 HttpServletRequest request){
        if (pageNum == null) {
            pageNum = 1;
        }
        User user = userService.getUserBySession(request);
        Map param = new HashMap();
        if (tag.length() > 0) {
            Tags tags = tagsMapper.queryByName(tag);
            if (tags != null) {
                param.put("tagId", tags.getTag_id());
            }
        }

        if (user != null && (privilegeMapper.query(user.getUsername(), "HEAD") != null
                || privilegeMapper.query(user.getUsername(), "ADMIN") != null)) {
        } else {
            param.put("defunct", 'N');
        }
        PageHelper.startPage(pageNum,pageSize);
        Page<Problem> problems = problemMapper.findByPaging(param);
        for (Problem problem: problems.subList(0, problems.size())) {
            List<Tags> tags = new ArrayList<>();
            for (Tagsview tagsview: tagsviewMapper.queryByProblems(problem.getProblem_id())) {
                tags.add(tagsMapper.query(tagsview.getTag_id()));
            }
            problem.setTags(tags);
            problem.setStatus(0);
            if (user != null) {
                if (solutionMapper.getProblemTriedCount(user.getUsername(), problem.getProblem_id(), 0) > 0) {
                    if (solutionMapper.getProblemSolvedCount(user.getUsername(), problem.getProblem_id(), 0) > 0) {
                        problem.setStatus(1);
                    } else {
                        problem.setStatus(2);
                    }
                }
            }
        }

        PageInfo<Problem> pageInfo = new PageInfo<>(problems);
        ModelAndView mv = new ModelAndView();
        mv.addObject("pageInfo", pageInfo);
        mv.setViewName("problem/problemset.html");
        return mv;
    }

    @GetMapping("/problemset/problem/{problemId}")
    public ModelAndView problem(@PathVariable String problemId, HttpServletRequest request){
        Problem problem = problemService.queryAdmin(Integer.parseInt(problemId));
        if (problem == null) {
            throw new OJException(CodeMsg.PROBLEM_NO_SUCH);
        }
        if (problem.getDefunct().equals("Y")) {
            User user = userService.getUserBySession(request);
            if (user == null
                    || (privilegeMapper.query(user.getUsername(), "HEAD") == null
                    && privilegeMapper.query(user.getUsername(), "ADMIN") == null)) {
                throw new OJException(CodeMsg.PROBLEM_NO_SUCH);
            }
        }

        List<Tags> tags = new ArrayList<>();
        for (Tagsview tagsview: tagsviewMapper.queryByProblems(problem.getProblem_id())) {
            tags.add(tagsMapper.query(tagsview.getTag_id()));
        }
        problem.setTags(tags);
        List<Language> langs = OJUtil.getActiveLanguages("GUET");
        ModelAndView mv = new ModelAndView();
        mv.addObject("problem", problem);
        mv.addObject("languages", langs);
        mv.setViewName("problem/problem.html");
        return mv;
    }

    @GetMapping("/contest/{contestId}/problem/{num}")
    public ModelAndView problem(@PathVariable String contestId, @PathVariable String num){
        ContestProblem cp = contestProblemMapper.query(Integer.parseInt(contestId), num);
        if (cp == null) {
            throw new OJException(CodeMsg.PROBLEM_NO_SUCH);
        }
        Problem problem = problemService.queryAdmin(cp.getProblem_id());
        List<Language> langs = OJUtil.getActiveLanguages("GUET");
        ModelAndView mv = new ModelAndView();
        mv.addObject("contestProblem", cp);
        mv.addObject("problem", problem);
        mv.addObject("languages", langs);
        mv.setViewName("contest/problem.html");
        return mv;
    }

    @RequestMapping(value = {"/admin/problem/new"}, method = RequestMethod.GET)
    public ModelAndView problemNew(Model model){
        try {
            Problem problem = new Problem();
            if (problem == null) {
                return null;
            }
            ModelAndView mv=new ModelAndView();
            mv.addObject("problem", problem);
            mv.setViewName("admin/problem-edit.html");
            return mv;
        }catch (Exception e) {
        }
        return null;
    }

    @GetMapping(value = {"/admin/problem/edit/{problemId}"})
    public ModelAndView edit(@PathVariable(value = "problemId",required = false)Integer problemId,
                       HttpServletRequest request){
        try {
            Problem problem = problemMapper.queryAdmin(problemId);
            if (problem == null) {
                throw new OJException(CodeMsg.PROBLEM_NO_SUCH);
            }

            String tagsList = new String();
            List<Tagsview> tagsview = tagsviewMapper.queryByProblems(problemId);
            for (Tagsview t:tagsview) {
                Tags tag = tagsMapper.query(t.getTag_id());
                if (tagsList.length() == 0) {
                    tagsList += tag.getName();
                } else {
                    tagsList += "," + tag.getName();
                }
            }
            problem.setTag(tagsList);
            ModelAndView mv=new ModelAndView();
            mv.addObject("problem", problem);
            mv.setViewName("admin/problem-edit.html");
            return mv;
        }catch (Exception e) {
        }
        return null;
    }

    @ResponseBody
    @PostMapping(value = "/api/admin/problem/post")
    public Result postProblem(@RequestParam Map<String, Object> map, HttpServletRequest request){
        //log.info(map.toString());

        Integer problemId = 0;
        if (map.get("problemId") != null && map.get("problemId").toString().length() != 0) {
            problemId = Integer.parseInt(map.get("problemId").toString());
        }

        if (problemId == 0) {
            return newProblem(map, request);
        } else {
            return editProblem(map, request);
        }
    }
    public Result newProblem(Map<String, Object> map, HttpServletRequest request) {
        try {
            Problem problem = getProblemByReqMap(map);
            if (problem == null) {
                return Result.error(CodeMsg.PARAM_NULL.locale());
            }

            Date dt = new Date();
            SimpleDateFormat simpleDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            problem.setCreate_date(Timestamp.valueOf(simpleDate.format(dt)));
            problem.setAuthor(request.getSession().getAttribute("session_username").toString());

            String tags = problem.getTag();
            if (problemMapper.insert(problem)) {
                saveProblemTags(problem.getProblem_id(), tags, request);
                return Result.success();
            }
            log.error("Problem insert failed.");
        } catch (Exception e) {
            log.error("newProblem Exception."+ e.getMessage());
        }
        return Result.error(CodeMsg.INNER_FAULT.locale());
    }
    public Result editProblem(Map<String, Object> map, HttpServletRequest request) {
        try {
            Problem problem = getProblemByReqMap(map);
            if (problem == null) {
                return Result.error(CodeMsg.PARAM_NULL.locale());
            }

            if (map.get("title").toString().length() == 0 || map.get("problemId") == null) {
                return Result.error(CodeMsg.PARAM_NULL.locale());
            }

            Integer problemId = Integer.parseInt(map.get("problemId").toString());
            if (problemMapper.queryAdmin(problemId) == null) {
                return Result.error(CodeMsg.INNER_FAULT.locale());
            }

            saveProblemTags(problemId, problem.getTag(), request);
            problem.setProblem_id(problemId);

            if (problemMapper.update(problem)) {
                return Result.success();
            }
            log.error("Problem update failed.");
        } catch (Exception e) {
            log.error("editProblem Exception."+ e.getMessage());
        }
        return Result.error(CodeMsg.INNER_FAULT.locale());
    }

    public boolean saveProblemTags(Integer problemId, String tags, HttpServletRequest request) {
        tagsviewMapper.deleteByProblemId(problemId);
        String[] tagsString = tags.split(",");
        for(String t_:tagsString){
            t_ = t_.trim();
            if (t_.length() == 0) {
                continue;
            }

            Tags tag_ = tagsMapper.queryByName(t_);
            if(tag_== null){
                tag_ = new Tags();
                tag_.setCreate_user(userService.getUserBySession(request).getUsername());
                tag_.setIndate(new Date());
                tag_.setName(t_);
                tagsMapper.insert(tag_);
            }

            Tagsview tagsview_ = new Tagsview();
            tagsview_.setTag_id(tag_.getTag_id());
            tagsview_.setProblem_id(problemId);
            tagsviewMapper.insert(tagsview_);
        }
        return true;
    }

    public Problem getProblemByReqMap(Map<String, Object> map) {
        if (map.get("title") == null) {
            return null;
        }

        Problem problem = new Problem();
        problem.setTitle(map.get("title").toString());

        if (map.get("timeLimit") != null) {
            problem.setTime_limit(Integer.parseInt(map.get("timeLimit").toString()));
        }
        if (map.get("memLimit") != null) {
            problem.setMemory_limit(Integer.parseInt(map.get("memLimit").toString()));
        }
        if (map.get("ojName") != null) {
            problem.setOj_name(map.get("ojName").toString());
        }
        if (map.get("disable") != null) {
            problem.setDefunct(map.get("disable").toString());
        }
        if (map.get("desc") != null) {
            problem.setDescription(map.get("desc").toString());
        }
        if (map.get("input") != null) {
            problem.setInput(map.get("input").toString());
        }
        if (map.get("output") != null) {
            problem.setOutput(map.get("output").toString());
        }
        if (map.get("sampleInput") != null) {
            problem.setSample_input(map.get("sampleInput").toString());
        }
        if (map.get("sampleOutput") != null) {
            problem.setSample_output(map.get("sampleOutput").toString());
        }
        if (map.get("note") != null) {
            problem.setHint(map.get("note").toString());
        }
        if (map.get("tags") != null) {
            problem.setTag(map.get("tags").toString());
        }
        return problem;
    }

    @ResponseBody
    @RequestMapping(value = {"/api/admin/problem/testcases"}, method = RequestMethod.GET)
    public Result<List> apiProblemData(Integer problemId){
        String basePath = OJConfig.getDataPath() + File.separator + problemId.toString();
        List<File> f_list = StreamHandler.getFileList(basePath);

        List<OJTestCaseFile> testFiles = new ArrayList<OJTestCaseFile>();
        for (File f:f_list) {
            if (!f.getName().endsWith(".in")) {
                continue;
            }

            OJTestCaseFile testFile = new OJTestCaseFile();
            testFile.setProblemId(problemId);
            String outFilePath = f.toString().split("\\.")[0] + ".out";
            File iFile = new File(f.toString());
            if (iFile.exists()) {
                testFile.setInFileName(f.getName());
                testFile.setInputString(StreamHandler.readEx(iFile).replaceAll("<", "&lt;")
                        .replaceAll(">", "&gt;")
                        .replaceAll("\n", "<br>")
                        .replaceAll(" ", "&nbsp;"));
            }

            File oFile = new File(outFilePath);
            if (oFile.exists()) {
                testFile.setOutFileName(oFile.getName());
                testFile.setOutputString(StreamHandler.readEx(oFile).replaceAll("<", "&lt;")
                        .replaceAll(">", "&gt;")
                        .replaceAll("\n", "<br>")
                        .replaceAll(" ", "&nbsp;"));
            }
            testFiles.add(testFile);
        }

        Collections.sort(testFiles, new Comparator<OJTestCaseFile>() {
            public int compare(OJTestCaseFile r1, OJTestCaseFile r2) {
                int l = Integer.compare(r1.getInFileName().length(), r2.getInFileName().length());
                if (l > 0) {
                    return 1;
                } else if (l < 0) {
                    return -1;
                } else {
                    return r1.getInFileName().compareTo(r2.getInFileName());
                }
            }
        });
        return Result.success(testFiles);
    }

    @ResponseBody
    @PostMapping(value = "/api/admin/problem/testcases/delete")
    public Result postMessage(@RequestParam("problemId") Integer problemId,
                              @RequestParam("caseName") String caseName,
                              HttpServletRequest request){
        String basePath = OJConfig.getDataPath() + File.separator + problemId.toString();
        String inFilePath = basePath + File.separator + caseName + ".in";
        String outFilePath = basePath + File.separator + caseName + ".out";
        File iFile = new File(inFilePath);
        if (iFile.exists()) {
            iFile.delete();
        }
        File oFile = new File(outFilePath);
        if (oFile.exists()) {
            oFile.delete();
        }
        return Result.success();
    }

    @ResponseBody
    @PostMapping(value = "/api/admin/problem/testcases/modify")
    public Result postMessage(@RequestParam("problemId") Integer problemId,
                              @RequestParam("caseName") String caseName,
                              @RequestParam("inputString") String inputString,
                              @RequestParam("outputString") String outputString,
                              HttpServletRequest request){
        String basePath = OJConfig.getDataPath() + File.separator + problemId.toString();
        String inFilePath = basePath + File.separator + caseName + ".in";
        String outFilePath = basePath + File.separator + caseName + ".out";
        log.info(inFilePath);
        log.info(inputString);
        log.info(outFilePath);
        log.info(outputString);
        File iFile = new File(inFilePath);
        if (iFile.exists()) {
        }
        StreamHandler.write(inFilePath, inputString);
        File oFile = new File(outFilePath);
        if (oFile.exists()) {
        }
        StreamHandler.write(outFilePath, outputString);
        return Result.success();
    }

    @GetMapping("/problem/{problemId}/editorial/new")
    public ModelAndView editorialNew(@PathVariable(value = "problemId",required = false)Integer problemId,
                                     HttpServletRequest request){
        try {
            Problem problem = problemService.query(problemId);
            if (problem == null) {
                throw new OJException(CodeMsg.PROBLEM_NO_SUCH);
            }
            Message message = new Message();
            if (message == null) {
                return null;
            }
            message.setMessage_id(0);
            message.setParent_id(0);
            message.setRoot_id(0);
            message.setProblem_id(problemId);
            ModelAndView mv=new ModelAndView();
            mv.addObject("message", message);
            mv.setViewName("topic/topic-edit.html");
            return mv;
        }catch (Exception e) {
        }
        return null;
    }
}